#include <errno.h>
#include <stdio.h>
#include <alloca.h>
#include "hash.h"
#include "libzyfp.h"

/*********************| 数据定义 |*********************/
#define ZYFP_F_WITHPWD		0x2
#define ZYFP_F_LOCK		0x4
#define ZYFP_HEAD_MAGIC		"zyfp"
#define ZYFP_HEAD_MAGICLEN	4
#define ZYFP_PASSHASH_TAIL	"#aD$3bY@++=nbew"
#define PACK_HEAD_LEN		(ZYFP_HEAD_MAGICLEN + sizeof(int16_t) + sizeof(uint32_t) +sizeof(time_t) + 1)
#define READ_PERMISSION_CHECK(ph) \
	if (ph->flag & ZYFP_O_WRITE) return ZYFP_ERR_PERMISSION;\
	if (ph->flag & ZYFP_F_LOCK)  return ZYFP_ERR_PASSNOMATCH

#define READ_PERMISSION_CHECKFIDX(ph) \
	READ_PERMISSION_CHECK(ph);\
	if (!ph->fnow) return ZYFP_ERR_PERMISSION

typedef int (*_zyfp_cmp_fn_t) (const void *, const void *);

// 文件数据索引结构
typedef struct {
	zyfp_hash_t	hash;
	uint32_t	l;
	uint32_t	b;
} fdata_idx;

struct zyfp_handle_t {
	FILE*		fp;
	int		flag;		// 文件标记
	fdata_idx*	fnow;		// 当前读取文件的文件索引
	long		fnowpos;	// 当前文件的文件位置指针
	fdata_idx*	fidxarr;	// 文件数据索引数组
	unsigned char	passwd[ZYFP_MD5_LEN];
	uint16_t	passpos;	// 密码位置
	int16_t		ver;		// 版本号
	uint32_t	filenum;	// 打包文件个数
	time_t		packtime;	// 打包日期
};

/*********************| 内部方法 |*********************/
static void pack_passmd5(unsigned char* md5)
{
	uint32_t keylen = strlen(ZYFP_PASSHASH_TAIL) + ZYFP_MD5_LEN;
	char* tmp = (char*)alloca(keylen);
	memcpy(tmp, md5, ZYFP_MD5_LEN);
	memcpy(tmp + ZYFP_MD5_LEN, ZYFP_PASSHASH_TAIL, keylen - ZYFP_MD5_LEN);
	zyfp_md5(tmp, keylen, md5);
}


static zyfp_handle* handle4write(FILE* fp, const char* key, size_t keylen)
{
	zyfp_handle* ret = (zyfp_handle*)malloc(sizeof(zyfp_handle));
	memset(ret, 0, sizeof(zyfp_handle));
	ret->fp = fp;
	ret->flag |= ZYFP_O_WRITE;
	ret->ver = ZYFP_LIB_VERSION;
	fwrite(ZYFP_HEAD_MAGIC, ZYFP_HEAD_MAGICLEN, 1, fp);
	fwrite(&ret->ver, sizeof(int16_t), 1, fp);
	char fill[PACK_HEAD_LEN - ZYFP_HEAD_MAGICLEN - sizeof(int16_t)];
	fwrite(fill, sizeof(fill), 1, fp);
	if (keylen) {
		ret->flag |= ZYFP_F_WITHPWD;
		zyfp_md5(key, (uint32_t)keylen, ret->passwd);
		fwrite(ret->passwd, sizeof(ret->passwd), 1, fp);
	}
	return ret;
}

static void encrypt_data(zyfp_handle* ph, char* out, size_t buflen)
{
	size_t i;
	for (i = 0; i < buflen; ++i) {
		out[i] ^= ph->passwd[ph->passpos];
		++ph->passpos;
		if (ph->passpos >= sizeof(ph->passwd))
			ph->passpos = 0;
	}
}

#define decrypt_data 	encrypt_data

static zyfp_handle* handle4read(FILE* fp, const char* key, size_t keylen)
{
	// 读取文件头
	static const size_t readl = sizeof(fdata_idx) - sizeof(uint32_t);
	char* headbuf = (char*)alloca(PACK_HEAD_LEN);
	if (fread(headbuf, PACK_HEAD_LEN, 1, fp) < 1 || strncmp(headbuf, ZYFP_HEAD_MAGIC, ZYFP_HEAD_MAGICLEN)) {
		zyfp_log("%s\n", zyfp_errmsg(ZYFP_ERR_PACK));
		goto do_err;
	}
	unsigned char fmd5[ZYFP_MD5_LEN];
	if (headbuf[PACK_HEAD_LEN - 1]) {
		if (fread(fmd5, ZYFP_MD5_LEN, 1, fp) < 1) {
			zyfp_log("%s\n", zyfp_errmsg(ZYFP_ERR_PACKDAMAGE));
			goto do_err;
		}
	}
	zyfp_handle* ret = (zyfp_handle*)malloc(sizeof(zyfp_handle));
	memset(ret, 0, sizeof(zyfp_handle));
	ret->fp = fp;
	headbuf += ZYFP_HEAD_MAGICLEN;
	memcpy(&ret->ver, headbuf, sizeof(ret->ver));
	headbuf += sizeof(ret->ver);
	memcpy(&ret->filenum, headbuf, sizeof(ret->filenum));
	headbuf += sizeof(ret->filenum);
	memcpy(&ret->packtime, headbuf, sizeof(ret->packtime));
	headbuf += sizeof(ret->packtime);
	uint32_t offset  = PACK_HEAD_LEN;
	if (*headbuf) {
		offset += ZYFP_MD5_LEN;
		ret->flag |= ZYFP_F_WITHPWD;
		if (!keylen) {
			ret->flag |= ZYFP_F_LOCK;
		} else {
			unsigned char md5[ZYFP_MD5_LEN];
			zyfp_md5(key, keylen, md5);
			memcpy(ret->passwd, md5, ZYFP_MD5_LEN);
			pack_passmd5(md5);
			if (memcmp(md5, fmd5, ZYFP_MD5_LEN))
				ret->flag |= ZYFP_F_LOCK;
		}
	}
	// 读取所有的文件索引数据
	if (ret->filenum) {
		uint32_t i = 0;
		fdata_idx* pidx;
		ret->fidxarr = (fdata_idx*)malloc(sizeof(fdata_idx) * ret->filenum);
		for (;i < ret->filenum; ++i) {
			pidx = &ret->fidxarr[i];
			pidx->b = offset + readl;
			fread(pidx, readl, 1, fp);
			offset += pidx->l + readl;
			fseek(fp, (long)offset, SEEK_SET);
		}
		qsort(ret->fidxarr, ret->filenum, sizeof(fdata_idx), (_zyfp_cmp_fn_t)zyfp_hash_cmp);
	}
	return ret;
do_err:
	fclose(fp);
	return NULL;
}

/*********************| 外部接口 |*********************/
zyfp_handle*zyfp_init(const char* ppath, const char* key, int flag)
{
	size_t keylen = strlen(key);
	// 打开文件
	FILE* fp = NULL;
	if (flag & ZYFP_O_WRITE) {
		fp = fopen(ppath, "wb");
		if (!fp) {
			zyfp_log("%s\n", zyfp_errmsg(ZYFP_ERR_SYS));
			return NULL;
		}
		return handle4write(fp, key, keylen);
	} else {
		fp = fopen(ppath, "rb");
		if (!fp) {
			zyfp_log("%s\n", zyfp_errmsg(ZYFP_ERR_SYS));
			return NULL;
		}
		return handle4read(fp, key, keylen);
	}
}

int zyfp_end(zyfp_handle* ph)
{
	if (!ph) {
		zyfp_log("%s\n", zyfp_errmsg(ZYFP_ERR_ARG));
		return 0;
	}
	int ret = 0;
	if ((ph->flag & ZYFP_O_WRITE)) {
		if (ph->filenum) {
			// 写入文件头信息
			ph->packtime = time(NULL);
			pack_passmd5(ph->passwd);
			fseek(ph->fp, ZYFP_HEAD_MAGICLEN + sizeof(int16_t), SEEK_SET);
			fwrite(&ph->filenum, sizeof(ph->filenum), 1, ph->fp);
			fwrite(&ph->packtime, sizeof(ph->packtime), 1, ph->fp);
			int8_t w = (ph->flag & ZYFP_F_WITHPWD) ? 1 : 0;
			fwrite(&w, 1, 1, ph->fp);
			if (w) {
				fwrite(&ph->passwd, sizeof(ph->passwd), 1, ph->fp);
			}
		} else {
			ret = 1;
		}
	} else if (ph->fidxarr) {
		free(ph->fidxarr);
	}
	fclose(ph->fp);
	free(ph);
	return ret;
}

ssize_t	zyfp_open_ex(zyfp_handle* ph, const char* path, uint32_t pl)
{
	if (!ph || pl < 1) return ZYFP_ERR_ARG;
	READ_PERMISSION_CHECK(ph);
	if (ph->ver != ZYFP_LIB_VERSION)
		return ZYFP_VER_NOTMATCH;
	fdata_idx idx;
	zyfp_hash(path, pl, idx.hash);
	fdata_idx* tmp = (fdata_idx*)bsearch(&idx, ph->fidxarr, ph->filenum, sizeof(fdata_idx), (_zyfp_cmp_fn_t)zyfp_hash_cmp);
	if (!tmp)
		return ZYFP_FILE_NOTFOUND;
	if (fseek(ph->fp, (long)tmp->b, SEEK_SET) != 0)
		return ZYFP_ERR_SYS;
	ph->fnow = tmp;
	ph->passpos = 0;
	ph->fnowpos = 0;
	return (ssize_t)ph->fnow->l;
}

ssize_t zyfp_open(zyfp_handle* ph, const char* path)
{
	return zyfp_open_ex(ph, path, (uint32_t)strlen(path));
}

ssize_t zyfp_read(zyfp_handle* ph, ssize_t n, char* out)
{
	if (!ph || n < 1 || !out) return ZYFP_ERR_ARG;
	READ_PERMISSION_CHECKFIDX(ph);
	ssize_t r = (ssize_t)(ph->fnow->l - ph->fnowpos);
	if (r <= 0)
		return 0;
	if (n > r)
		n = r;
	r = fread(out, 1, n, ph->fp);
	ph->fnowpos += (long)r;
	if (ph->flag & ZYFP_F_WITHPWD)
		decrypt_data(ph, out, (size_t)r);
	return r;
}

ssize_t zyfp_readall(zyfp_handle* ph, char** out)
{
	if (!ph) return ZYFP_ERR_ARG;
	READ_PERMISSION_CHECKFIDX(ph);
	if (fseek(ph->fp, (long)ph->fnow->b, SEEK_SET) != 0)
		return ZYFP_ERR_SYS;
	*out = (char*)malloc(ph->fnow->l);
	if (1 != fread(*out, ph->fnow->l, 1, ph->fp)) {
		// 恢复原位置
		fseek(ph->fp, (long)ph->fnow->b + ph->fnowpos, SEEK_SET);
		free(*out);
		return ZYFP_ERR_SYS;
	}
	ph->fnowpos = (long)ph->fnow->l;
	if (ph->flag & ZYFP_F_WITHPWD) {
		ph->passpos =  0;
		decrypt_data(ph, *out, ph->fnow->l);
	}
	return (ssize_t)ph->fnow->l;
}

int zyfp_seek(zyfp_handle* ph, long offset, int whence)
{
	if (!ph)  return ZYFP_ERR_ARG;
	READ_PERMISSION_CHECKFIDX(ph);
	// 全部转换成SEEK_SET需要的偏移
	switch (whence) {
	case SEEK_SET:
		break;
	case SEEK_CUR:
		offset += ph->fnowpos;
		break;
	case SEEK_END:
		offset += (long)ph->fnow->l;
		break;
	default:
		return ZYFP_ERR_ARG;
	}
	if (offset < 0)
		offset = 0;
	if (offset > (long)ph->fnow->l)
		offset = ph->fnow->l;
	if(fseek(ph->fp, offset + ph->fnow->b, SEEK_SET) != 0)
		return ZYFP_ERR_SYS;
	// 调整当前文件的文件指针
	ph->fnowpos = offset;
	// 调整密钥指针
	if (ph->flag & ZYFP_F_WITHPWD)
		ph->passpos = offset % sizeof(ph->passwd);
	return ZYFP_ERR_NONE;
}

ssize_t zyfp_tell(const zyfp_handle* ph)
{
	if (!ph)  return ZYFP_ERR_ARG;
	READ_PERMISSION_CHECKFIDX(ph);
	return (ssize_t)ph->fnowpos;
}

int zyfp_eof(const zyfp_handle* ph)
{
	if (!ph)  return ZYFP_ERR_ARG;
	READ_PERMISSION_CHECKFIDX(ph);
	return (ph->fnowpos == (long)ph->fnow->l) ? 1 : 0;
}

int zyfp_gethead(const zyfp_handle* ph, zyfp_head* out)
{
	if (!ph || !out) return ZYFP_ERR_ARG;
	out->encrypt = (ph->flag & ZYFP_F_WITHPWD) ? 1 : 0;
	out->passmatch = !(ph->flag & ZYFP_F_LOCK);
	out->version = ph->ver;
	out->filenum = ph->filenum;
	out->packtime = ph->packtime;
	return ZYFP_ERR_NONE;
}

int zyfp_passmatch(const zyfp_handle* ph)
{
	if (!ph) return 0;
	return !(ph->flag & ZYFP_F_LOCK);
}

uint32_t zyfp_packfilenum(const zyfp_handle* ph)
{
	if (!ph) return 0;
	return ph->filenum;
}

int zyfp_add(zyfp_handle* ph, const char* path, uint32_t pl)
{
	if (!ph || !path || pl < 1) return ZYFP_ERR_ARG;
	if (!(ph->flag & ZYFP_O_WRITE)) return ZYFP_ERR_PERMISSION;
	zyfp_hash_t hash;
	zyfp_hash(path, pl, hash);
	FILE* fp = fopen(path, "rb");
	if (!fp)
		return ZYFP_ERR_SYS;
	char buf[10240];
	size_t rn;
	fseek(fp, 0, SEEK_END);
	uint32_t total = (uint32_t)ftell(fp);
	fseek(fp, 0, SEEK_SET);
	fwrite(hash, sizeof(hash), 1, ph->fp);
	fwrite(&total, sizeof(total), 1, ph->fp);
	if (ph->flag & ZYFP_F_WITHPWD) {
		ph->passpos = 0;
		while(!feof(fp)) {
			rn = fread(buf, 1, sizeof(buf), fp);
			encrypt_data(ph, buf, rn);
			fwrite(buf, rn, 1, ph->fp);
		}
	} else {
		while(!feof(fp)) {
			rn = fread(buf, 1, sizeof(buf), fp);
			fwrite(buf, rn, 1, ph->fp);
		}
	}
	++ph->filenum;
	fclose(fp);
	return ZYFP_ERR_NONE;
}

const char*zyfp_errmsg(int err)
{
	switch (err) {
	case ZYFP_ERR_NONE:
		return "没有出现错误";
	case ZYFP_ERR_SYS:
		return strerror(errno);
	case ZYFP_ERR_ARG:
		return "参数错误";
	case ZYFP_ERR_PERMISSION:
		return "权限不符";
	case ZYFP_ERR_PASSNOMATCH:
		return "密码不正确";
	case ZYFP_ERR_PACK:
		return "文件非zyf package";
	case ZYFP_ERR_PACKDAMAGE:
		return "归档文件破损";
	case ZYFP_FILE_NOTFOUND:
		return "文件未找到";
	case ZYFP_VER_NOTMATCH:
		return "文件版本和库版本不合";
	default:
		return "未知错误类型";
	}
}
